"use strict";
var __extends = this && this.__extends || function () {
  var extendStatics = function (d, b) {
    extendStatics = Object.setPrototypeOf || {
      __proto__: []
    } instanceof Array && function (d, b) {
      d.__proto__ = b;
    } || function (d, b) {
      for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
    };
    return extendStatics(d, b);
  };
  return function (d, b) {
    if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
    extendStatics(d, b);
    function __() {
      this.constructor = d;
    }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  };
}();
var __assign = this && this.__assign || function () {
  __assign = Object.assign || function (t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
      s = arguments[i];
      for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
    }
    return t;
  };
  return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.cloneNode = exports.hasChildren = exports.isDocument = exports.isDirective = exports.isComment = exports.isText = exports.isCDATA = exports.isTag = exports.Element = exports.Document = exports.CDATA = exports.NodeWithChildren = exports.ProcessingInstruction = exports.Comment = exports.Text = exports.DataNode = exports.Node = void 0;
var domelementtype_1 = require("../domelementtype/index.js");
/**
 * This object will be used as the prototype for Nodes when creating a
 * DOM-Level-1-compliant structure.
 */
var Node = /** @class */function () {
  function Node() {
    /** Parent of the node */
    this.parent = null;
    /** Previous sibling */
    this.prev = null;
    /** Next sibling */
    this.next = null;
    /** The start index of the node. Requires `withStartIndices` on the handler to be `true. */
    this.startIndex = null;
    /** The end index of the node. Requires `withEndIndices` on the handler to be `true. */
    this.endIndex = null;
  }
  Object.defineProperty(Node.prototype, "parentNode", {
    // Read-write aliases for properties
    /**
     * Same as {@link parent}.
     * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
     */
    get: function () {
      return this.parent;
    },
    set: function (parent) {
      this.parent = parent;
    },
    enumerable: false,
    configurable: true
  });
  Object.defineProperty(Node.prototype, "previousSibling", {
    /**
     * Same as {@link prev}.
     * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
     */
    get: function () {
      return this.prev;
    },
    set: function (prev) {
      this.prev = prev;
    },
    enumerable: false,
    configurable: true
  });
  Object.defineProperty(Node.prototype, "nextSibling", {
    /**
     * Same as {@link next}.
     * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
     */
    get: function () {
      return this.next;
    },
    set: function (next) {
      this.next = next;
    },
    enumerable: false,
    configurable: true
  });
  /**
   * Clone this node, and optionally its children.
   *
   * @param recursive Clone child nodes as well.
   * @returns A clone of the node.
   */
  Node.prototype.cloneNode = function (recursive) {
    if (recursive === void 0) {
      recursive = false;
    }
    return cloneNode(this, recursive);
  };
  return Node;
}();
exports.Node = Node;
/**
 * A node that contains some data.
 */
var DataNode = /** @class */function (_super) {
  __extends(DataNode, _super);
  /**
   * @param data The content of the data node
   */
  function DataNode(data) {
    var _this = _super.call(this) || this;
    _this.data = data;
    return _this;
  }
  Object.defineProperty(DataNode.prototype, "nodeValue", {
    /**
     * Same as {@link data}.
     * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
     */
    get: function () {
      return this.data;
    },
    set: function (data) {
      this.data = data;
    },
    enumerable: false,
    configurable: true
  });
  return DataNode;
}(Node);
exports.DataNode = DataNode;
/**
 * Text within the document.
 */
var Text = /** @class */function (_super) {
  __extends(Text, _super);
  function Text() {
    var _this = _super !== null && _super.apply(this, arguments) || this;
    _this.type = domelementtype_1.ElementType.Text;
    return _this;
  }
  Object.defineProperty(Text.prototype, "nodeType", {
    get: function () {
      return 3;
    },
    enumerable: false,
    configurable: true
  });
  return Text;
}(DataNode);
exports.Text = Text;
/**
 * Comments within the document.
 */
var Comment = /** @class */function (_super) {
  __extends(Comment, _super);
  function Comment() {
    var _this = _super !== null && _super.apply(this, arguments) || this;
    _this.type = domelementtype_1.ElementType.Comment;
    return _this;
  }
  Object.defineProperty(Comment.prototype, "nodeType", {
    get: function () {
      return 8;
    },
    enumerable: false,
    configurable: true
  });
  return Comment;
}(DataNode);
exports.Comment = Comment;
/**
 * Processing instructions, including doc types.
 */
var ProcessingInstruction = /** @class */function (_super) {
  __extends(ProcessingInstruction, _super);
  function ProcessingInstruction(name, data) {
    var _this = _super.call(this, data) || this;
    _this.name = name;
    _this.type = domelementtype_1.ElementType.Directive;
    return _this;
  }
  Object.defineProperty(ProcessingInstruction.prototype, "nodeType", {
    get: function () {
      return 1;
    },
    enumerable: false,
    configurable: true
  });
  return ProcessingInstruction;
}(DataNode);
exports.ProcessingInstruction = ProcessingInstruction;
/**
 * A `Node` that can have children.
 */
var NodeWithChildren = /** @class */function (_super) {
  __extends(NodeWithChildren, _super);
  /**
   * @param children Children of the node. Only certain node types can have children.
   */
  function NodeWithChildren(children) {
    var _this = _super.call(this) || this;
    _this.children = children;
    return _this;
  }
  Object.defineProperty(NodeWithChildren.prototype, "firstChild", {
    // Aliases
    /** First child of the node. */
    get: function () {
      var _a;
      return (_a = this.children[0]) !== null && _a !== void 0 ? _a : null;
    },
    enumerable: false,
    configurable: true
  });
  Object.defineProperty(NodeWithChildren.prototype, "lastChild", {
    /** Last child of the node. */
    get: function () {
      return this.children.length > 0 ? this.children[this.children.length - 1] : null;
    },
    enumerable: false,
    configurable: true
  });
  Object.defineProperty(NodeWithChildren.prototype, "childNodes", {
    /**
     * Same as {@link children}.
     * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
     */
    get: function () {
      return this.children;
    },
    set: function (children) {
      this.children = children;
    },
    enumerable: false,
    configurable: true
  });
  return NodeWithChildren;
}(Node);
exports.NodeWithChildren = NodeWithChildren;
var CDATA = /** @class */function (_super) {
  __extends(CDATA, _super);
  function CDATA() {
    var _this = _super !== null && _super.apply(this, arguments) || this;
    _this.type = domelementtype_1.ElementType.CDATA;
    return _this;
  }
  Object.defineProperty(CDATA.prototype, "nodeType", {
    get: function () {
      return 4;
    },
    enumerable: false,
    configurable: true
  });
  return CDATA;
}(NodeWithChildren);
exports.CDATA = CDATA;
/**
 * The root node of the document.
 */
var Document = /** @class */function (_super) {
  __extends(Document, _super);
  function Document() {
    var _this = _super !== null && _super.apply(this, arguments) || this;
    _this.type = domelementtype_1.ElementType.Root;
    return _this;
  }
  Object.defineProperty(Document.prototype, "nodeType", {
    get: function () {
      return 9;
    },
    enumerable: false,
    configurable: true
  });
  return Document;
}(NodeWithChildren);
exports.Document = Document;
/**
 * An element within the DOM.
 */
var Element = /** @class */function (_super) {
  __extends(Element, _super);
  /**
   * @param name Name of the tag, eg. `div`, `span`.
   * @param attribs Object mapping attribute names to attribute values.
   * @param children Children of the node.
   */
  function Element(name, attribs, children, type) {
    if (children === void 0) {
      children = [];
    }
    if (type === void 0) {
      type = name === "script" ? domelementtype_1.ElementType.Script : name === "style" ? domelementtype_1.ElementType.Style : domelementtype_1.ElementType.Tag;
    }
    var _this = _super.call(this, children) || this;
    _this.name = name;
    _this.attribs = attribs;
    _this.type = type;
    return _this;
  }
  Object.defineProperty(Element.prototype, "nodeType", {
    get: function () {
      return 1;
    },
    enumerable: false,
    configurable: true
  });
  Object.defineProperty(Element.prototype, "tagName", {
    // DOM Level 1 aliases
    /**
     * Same as {@link name}.
     * [DOM spec](https://dom.spec.whatwg.org)-compatible alias.
     */
    get: function () {
      return this.name;
    },
    set: function (name) {
      this.name = name;
    },
    enumerable: false,
    configurable: true
  });
  Object.defineProperty(Element.prototype, "attributes", {
    get: function () {
      var _this = this;
      return Object.keys(this.attribs).map(function (name) {
        var _a, _b;
        return {
          name: name,
          value: _this.attribs[name],
          namespace: (_a = _this["x-attribsNamespace"]) === null || _a === void 0 ? void 0 : _a[name],
          prefix: (_b = _this["x-attribsPrefix"]) === null || _b === void 0 ? void 0 : _b[name]
        };
      });
    },
    enumerable: false,
    configurable: true
  });
  return Element;
}(NodeWithChildren);
exports.Element = Element;
/**
 * @param node Node to check.
 * @returns `true` if the node is a `Element`, `false` otherwise.
 */
function isTag(node) {
  return (0, domelementtype_1.isTag)(node);
}
exports.isTag = isTag;
/**
 * @param node Node to check.
 * @returns `true` if the node has the type `CDATA`, `false` otherwise.
 */
function isCDATA(node) {
  return node.type === domelementtype_1.ElementType.CDATA;
}
exports.isCDATA = isCDATA;
/**
 * @param node Node to check.
 * @returns `true` if the node has the type `Text`, `false` otherwise.
 */
function isText(node) {
  return node.type === domelementtype_1.ElementType.Text;
}
exports.isText = isText;
/**
 * @param node Node to check.
 * @returns `true` if the node has the type `Comment`, `false` otherwise.
 */
function isComment(node) {
  return node.type === domelementtype_1.ElementType.Comment;
}
exports.isComment = isComment;
/**
 * @param node Node to check.
 * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise.
 */
function isDirective(node) {
  return node.type === domelementtype_1.ElementType.Directive;
}
exports.isDirective = isDirective;
/**
 * @param node Node to check.
 * @returns `true` if the node has the type `ProcessingInstruction`, `false` otherwise.
 */
function isDocument(node) {
  return node.type === domelementtype_1.ElementType.Root;
}
exports.isDocument = isDocument;
/**
 * @param node Node to check.
 * @returns `true` if the node has children, `false` otherwise.
 */
function hasChildren(node) {
  return Object.prototype.hasOwnProperty.call(node, "children");
}
exports.hasChildren = hasChildren;
/**
 * Clone a node, and optionally its children.
 *
 * @param recursive Clone child nodes as well.
 * @returns A clone of the node.
 */
function cloneNode(node, recursive) {
  if (recursive === void 0) {
    recursive = false;
  }
  var result;
  if (isText(node)) {
    result = new Text(node.data);
  } else if (isComment(node)) {
    result = new Comment(node.data);
  } else if (isTag(node)) {
    var children = recursive ? cloneChildren(node.children) : [];
    var clone_1 = new Element(node.name, __assign({}, node.attribs), children);
    children.forEach(function (child) {
      return child.parent = clone_1;
    });
    if (node.namespace != null) {
      clone_1.namespace = node.namespace;
    }
    if (node["x-attribsNamespace"]) {
      clone_1["x-attribsNamespace"] = __assign({}, node["x-attribsNamespace"]);
    }
    if (node["x-attribsPrefix"]) {
      clone_1["x-attribsPrefix"] = __assign({}, node["x-attribsPrefix"]);
    }
    result = clone_1;
  } else if (isCDATA(node)) {
    var children = recursive ? cloneChildren(node.children) : [];
    var clone_2 = new CDATA(children);
    children.forEach(function (child) {
      return child.parent = clone_2;
    });
    result = clone_2;
  } else if (isDocument(node)) {
    var children = recursive ? cloneChildren(node.children) : [];
    var clone_3 = new Document(children);
    children.forEach(function (child) {
      return child.parent = clone_3;
    });
    if (node["x-mode"]) {
      clone_3["x-mode"] = node["x-mode"];
    }
    result = clone_3;
  } else if (isDirective(node)) {
    var instruction = new ProcessingInstruction(node.name, node.data);
    if (node["x-name"] != null) {
      instruction["x-name"] = node["x-name"];
      instruction["x-publicId"] = node["x-publicId"];
      instruction["x-systemId"] = node["x-systemId"];
    }
    result = instruction;
  } else {
    throw new Error("Not implemented yet: ".concat(node.type));
  }
  result.startIndex = node.startIndex;
  result.endIndex = node.endIndex;
  if (node.sourceCodeLocation != null) {
    result.sourceCodeLocation = node.sourceCodeLocation;
  }
  return result;
}
exports.cloneNode = cloneNode;
function cloneChildren(childs) {
  var children = childs.map(function (child) {
    return cloneNode(child, true);
  });
  for (var i = 1; i < children.length; i++) {
    children[i].prev = children[i - 1];
    children[i - 1].next = children[i];
  }
  return children;
}